package riofrio;

import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;

/**
 * A walk in a three.
 * 
 * The method that must be implemented is iterator().
 * 
 * @author Victor Hugo Herrera Maldonado
 */
public abstract class AbstractTreeWalk<T> implements Iterable<TreeNode<T>>{
    private final TreeNode<T> node;

    /**
     * Builds the instance.
     * 
     * @param node The three node to walk
     * @throws NullPointerException if node is null.
     */
    public AbstractTreeWalk(TreeNode<T> node) {
        Objects.requireNonNull(node, "node is null");
        this.node = node;
    }

    /**
     * Gets the node to walk.
     * 
     * @return Node to walk.
     */
    public TreeNode<T> getNode() {
        return node;
    }
    
    /**
     * Search the first node to match the predicate. Uses the order defined by this walk.
     * 
     * @param p predicate to match nodes.
     * @return The first node to match or null if there is no match.
     */
    public TreeNode<T> searchFirst(Predicate<TreeNode<T>> p) {
        Iterator<TreeNode<T>> iterator = iterator();
        while(iterator.hasNext()) {
            TreeNode<T> n = iterator.next();
            if(p.test(n)) {
                return n;
            }
        }
        return null;
    }

    /**
     * Search for all nodes that match the predicate. Uses the order defined by this walk.
     * 
     * @param p Predicate to match nodes.
     * @return All nodes that matches the predicate. If there is no match, the list is empty.
     */
    public List<TreeNode<T>> searchAll(Predicate<TreeNode<T>> p) {
        List<TreeNode<T>> nodes=new LinkedList<>();
        Iterator<TreeNode<T>> iterator = iterator();
        while(iterator.hasNext()) {
            TreeNode<T> n = iterator.next();
            if(p.test(n)) {
                nodes.add(n);
            }
        }
        return nodes;
    }

    /**
     * Evaluate each node to get a result. Nodes are evaluated in the order defined by this walk.
     * 
     * @param <E> Type of the result.
     * @param evaluator The interface to eval individual nodes.
     * @return The value of the root node of the walk.
     */
    public <E> E eval(Evaluator<T, E> evaluator) {
        Iterator<TreeNode<T>> iterator = iterator();
        IdentityHashMap<TreeNode<T>, E> results=new IdentityHashMap<>();
        while(iterator.hasNext()) {
            TreeNode<T> n = iterator.next();
            E result = evaluator.getValue(n, results);
            results.put(n, result);
        }
        return results.get(node);
    }
    
    /**
     * Return the nodes in the appropiate order for the specific walk.
     * @return The iterator. Modifications in the iterator must not affect the three.
     */
    @Override
    public abstract Iterator<TreeNode<T>> iterator();
    
}
